Reason analysis for Java ArrayList.toArray of T[] method whose parameter type is T instead of E

  • 2020-05-07 19:49:02
  • OfStack

Two days ago, I made code review to my colleague. I felt that I didn't master Generics of Java well enough, so I took out Effective Java 1 and looked at the relevant chapters. In section 1, Item 24: Eliminate unchecked warnings, the author takes public from the ArrayList class < T > The T[] toArray(T[] a) method is an example of how to use @SuppressWarnings annotation for variables.

ArrayList is an generic class, which states:


Javapublic class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

The toArray(T[] a) method of this class is an generic method, which is declared and implemented as follows:


@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

This method is actually declared in the Collection interface. Because we use it a lot with ArrayList, we use ArrayList as an example.

Why is 1 declared of a different type?

My question is: why does this method use type T instead of type E ?? That is, why isn't the method declared like this:


Javapublic E[] toArray(E[] a);

If the type is the same, the type error for the parameter can be found at compile time. If the type is different, it is easy to generate runtime errors. Here's the code:


// create 1 A type of  String  the  ArrayList
List<String> strList = new ArrayList<String>();
strList.add("abc");
strList.add("xyz");
// Will the current  strList  Converted to 1 a  Number  The array. Note that there are no compilation errors in the following statements. 
Number[] numArray = strList.toArray(new Number[0]);

Running the above code, Line 6 throws an java.lang.ArrayStoreException exception.

Statement 2 generates a compilation error if the toArray method USES type E. Compilation errors are more benign than runtime errors. Moreover, the main purpose of generics is to eliminate type conversion errors (ClassCastException) at compile time for type safety. This method does the opposite. Is this a big bug? I've run into bug, but I still can't believe this place is bug.

Look it up on the Internet, and it's been discussed many times: 2, 3, 4.

2 can improve the flexibility of

This declaration is more flexible and converts the elements in the current list into an array of a more 1-like type. For example, the current list type is Integer, and we can convert its elements into an Number array.


List<Integer> intList = new ArrayList<Integer>();
intList.add(1);
intList.add(2);
Number[] numArray = intList.toArray(new Number[0]);

If the method is declared as type E, the above code will have a compilation error. It seems more appropriate to declare the method as follows:


Javapublic <T super E> T[] toArray(T[] a);

However, < T super E > Such a syntax does not exist in Java. And even if it does, it doesn't work on arrays. For this reason, even if T is the parent of E, or T is the same as E, java.lang.ArrayStoreException exception 5, 6, 7 cannot be completely avoided when using this method. Look at the following two pieces of code. In the first code, T is the parent class of E. In the second code, T and E 1 are the same. Both pieces of code throw exceptions.

code 1:


List<Integer> intList = new ArrayList<Integer>();
intList.add(1);
intList.add(2); 
Float[] floatArray = new Float[2];
//Float  is  Number  Subclass, so  Float[]  is  Number[]  A subclass of 
Number[] numArray = floatArray;
// The following statement is thrown  ArrayStoreException  abnormal 
numArray = intList.toArray(numArray);

code 2:


List<Number> intList = new ArrayList<Number>();
//List  Is of type  Number . but  Number  Is an abstract class and can only store instances of its subclasses 
intList.add(new Integer());
intList.add(new Integer()); 
Float[] floatArray = new Float[];
//Float  is  Number  Subclass, so  Float[]  is  Number[]  A subclass of 
Number[] numArray = floatArray;
// The following statement is thrown  ArrayStoreException  abnormal 
numArray = intList.toArray(numArray);

The above exceptions are caused by the fact that if A is the parent of B, A[] is the parent of B[]. All classes in Java inherit from Object, and Object[] is the parent of all arrays.

An example in this post 8 shows that even if the method's type is declared as E, ArrayStoreException exceptions cannot be avoided.

The method's documentation also mentions this exception:


ArrayStoreException if the runtime type of the specified array is not a supertype of the runtime type of every element in this list.

3 is compatible with previous versions of Java 1.5

This method appeared before Java introduced Generics (Generics was introduced in JDK 1.5). At that time it was declared as follows:


Javapublic Object[] toArray(Object[] a)

After Generics appeared, many classes and methods became generic. This method is also declared as follows:


@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
0

This claims to be 10 compatible with previous versions of Java 1.5.

4

This method requires 1 array parameter. If the length of this array is greater than or equal to size of the current list, the elements of list will be stored in this array; If the length of this array is less than the size of the current list, a new array is created and the elements of the current list are stored in the newly created array. For efficiency, length of the passed array should be greater than or equal to size of list, if possible, to avoid this method from creating a new array.


@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
1

Also, the array as an argument cannot be null, otherwise an NullPointerException exception will be thrown.

Footnotes:

1
Effective Java (2nd Edition)
2
Link
3
Link
4
Link
5
Link
6
Link
7
Link
8
Link
9
Link
10
Link
Created: 2016-04-06 Wed 21:14
Emacs 24.5.1 (Org mode 8.2.10)
Validate

The above is the Java ArrayList.toArray(T[]) method parameter type is T instead of E reason analysis, hope to help you!


Related articles: